# coding: utf-8

import json
import urllib
import os
import time
import random

from datetime import datetime, timedelta, date
from sqlalchemy import func


from common.bid_notify.message import MessageNotify
from common.constant import BidQueueStatus, ZONE_STAT_EXCHANGE, VoucherStatus, \
    BID_USER_COUNT, COMMON_API_QUEUE, VoucherStatType, RedPacketStatus, \
    RECEIVE_RED_PACKET_HISTORY, IM_MESSAGE
from common.consumer import ConsumerBase
from common.demand_util import demand_to_complete, demand_bid_count, \
    demand_complete, gen_renewable_demand
from common.log import Log
from common.producer import Producer
from common.models import model_factory, DemandStatus, ParticStatus, FollowType, \
    DemandCate, EnterpriseLevel
from common.schedule.demand_bid_task import winner_handle_task, \
    robot_delay_update_status, bronze_winner_handle_task, bid_winner_call_notify
from common.schedule.demand_utils import bid_limit, clear_cache, \
    bind_virtual_number
from common.utils import DB, redis_client
from common.schedule.demand_task import demand_new_notify_task


class DemandWinnerConsumer(ConsumerBase):
    """
    create by ld_wu 2018-01-25
    中标的用户消费者
    """

    @staticmethod
    def msg_callback(channel, method, properties, body):
        params = json.loads(body)
        try:
            getattr(WinnerProcess(), params['func'])(
                *params['args'],
                **params['kwargs']
            )
            Log.info(body)
        except Exception as e:
            Log.info('DemandWinnerConsumer failed: {}'.format(e))


class AssignRedPacketConsumer(DemandWinnerConsumer):
    """
    add by zhouhl 2018-07-27
    逻辑跟 DemandWinnerConsumer 完全一样，仅方便维护
    """
    pass


class WinnerProcess(object):
    """
    create by ld_wu 2018-01-25
    1、修改中标人状态（bid_user_queues及partic）
    2、修改项目状态？
    3、绑定虚拟号
    4、解绑超时未联系的虚拟号及修改超时未联系状态
    5、发送中标通知
    6、清除缓存
    """

    @classmethod
    def assign_winner(cls, partic_ids, demand_id, published_count):
        with DB() as session:
            Log.info('demand:{} assign_winner'.format(demand_id))
            Log.info(partic_ids)
            partics = model_factory.partic.get_by_ids(session, partic_ids)
            for partic in partics:
                if partic.status != ParticStatus.WAITING.value:
                    return
            demand = model_factory.demand.get_by_id(session, demand_id)
            # 中标用户已达上限则派单抢单失败
            bid_count = demand_bid_count(demand.id, demand.published_count, session)
            if bid_count >= BID_USER_COUNT:
                demand_complete(demand)
                return
            current_count = session.query(model_factory.bid_user_queues).filter(
                model_factory.bid_user_queues.demand_id == demand_id,
                model_factory.bid_user_queues.published_count == published_count,
                model_factory.bid_user_queues.status.in_(
                    [BidQueueStatus.WAITING, BidQueueStatus.CALLED]
                )
            ).count()

            partic_mapping = {each.id: each for each in partics}
            limit = bid_limit(demand)
            Log.info('current_count:{}, limit: {}, partics: {}'.format(
                current_count, limit, partics
            ))
            winner_id = None
            mobile = None
            if current_count < limit:
                winner_id = partic_ids[0]
                waiting_ids = partic_ids[1:]
                partic = partic_mapping.get(winner_id)
                user = model_factory.user.get_by_id(session, partic.supplier_id)
                mobile = cls.be_winner(session, demand, user, partic)
                cls.be_waiting(session, waiting_ids, partic_mapping)

            else:
                cls.be_waiting(session, partic_ids, partic_mapping)

            # 如果人数已满则需要修改项目状态为竞价结束
            # 顺延以后不需要修改项目状态
            # if current_count + len(partic_ids) >= limit:
            #     if demand.status not in [200, 410]:
            #         demand.status = 100

            # cls.web_call(demand_id, published_count)
            if winner_id:
                cls.winner_handler(winner_id, demand_id, published_count, mobile)
        # 清除缓存
        Log.info('clear cache: {}'.format(demand_id))
        clear_cache(demand_id)

    @classmethod
    def be_waiting(cls, session, partic_ids, partic_mapping):
        """
        create by ld_wu 2018-02-01
        插入顺延等待队列
        :return:
        """
        for partic_id in partic_ids:
            partic = partic_mapping.get(partic_id)
            user = model_factory.user.get_by_id(session, partic.supplier_id)
            cls.insert_winner_queue(session, user, partic, BidQueueStatus.SPARE)

    @classmethod
    def insert_winner_queue(cls, session, user, partic, status):
        Log.info('in insert_winner_queue')

        # 派单,优先抢单不写入bid_user_queues
        if partic.demand_status in (
            DemandStatus.BRONZE_MEDAL_DISCOUNT.value,
            DemandStatus.DISPATCHING.value,
            DemandStatus.MASTER_DISPATCHING.value
        ):
            return

        queue = model_factory.bid_user_queues.get_by_args(session, partic_id=partic.id)
        if queue:
            queue.status = status
            queue.mobile = user.mobile
        else:
            model_factory.bid_user_queues.create(
                session,
                demand_id=partic.demand_id,
                partic_id=partic.id,
                user_id=partic.supplier_id,
                published_count=partic.published_count,
                action_id='',
                status=status,
                mobile=user.mobile
            )

    @classmethod
    def msg_notify(cls, session, demand, user, partic, called_number, pack):
        """
        :return:
        """
        # 发送抢单成功消息
        if partic.status == ParticStatus.NOT_CONTACT.value:
            MessageNotify.send_winner_msg(
                demand, user, partic, called_number, session
            )
        # 余额不足
        elif partic.status == ParticStatus.BALANCE_NOT_ENOUGH.value:
            MessageNotify.recharge_notify(demand, user, pack, partic, session)

    @classmethod
    def use_bid_point(cls, session, pack, partic, user):
        """
        create by ld_wu 2018-01-25
        扣费并写入消费记录
        :return:
        """
        # FIXME by ld_wu 2018-03-06 可能存在多次扣费的问题
        # 需要存储当前partic_id及项目状态来做是否扣费的标识

        Log.info('in use_bid_point')
        pack.bid_point -= partic.offer
        # 中标, 用户成就值加2(迁移到拨打电话之后再加成就值)
        # user.credit += 2
        model_factory.bid_cost_record.create(
            session,
            user_id=partic.supplier_id,
            cost_type=1,
            bid_point=partic.offer,
            left_bid_point=pack.bid_point,
            demand_id=partic.demand_id
        )
        producer = Producer(ZONE_STAT_EXCHANGE)
        producer.exchange_send(json.dumps({
            'cost_bid_point': float(partic.offer),
            'cost_bid_user_id': partic.supplier_id,
            'war_zone_id': user.war_zone_id
        }))
        common_producer = Producer(COMMON_API_QUEUE)
        common_producer.send(
            json.dumps(
                dict(
                    backend='bid_stock_stat',
                    func='day_stat',
                    kwargs=dict(
                        stat_type='cost_bid_point',
                        stat_cnt=int(partic.offer),
                    )
                )
            )
        )

    @classmethod
    def be_winner(cls, session, demand, user, partic, push_queue=True):
        """
        create by ld_wu 2018-01-25
        1、更改中标状态
        :return:
        """

        def update_status():
            partic.status = status
            partic.bid_win_at = int(time.time())

        Log.info('partic:{} be_winner'.format(partic.id))
        pack = session.query(model_factory.pack).filter_by(
            uid=partic.supplier_id,
            deleted_at=0
        ).with_for_update().first()

        tel = None
        if partic.is_robot:
            # 队列状态为待呼叫
            queue_status = BidQueueStatus.WAITING
            status = ParticStatus.NOT_CONTACT.value
            update_status()
            now = datetime.utcnow()
            delay_seconds = random.randint(60, 180)
            # 更新本次抢单记录的状态
            # 先于web_call task 执行，所以延时较短
            robot_delay_update_status.apply_async(
                (demand.id, partic.id, delay_seconds),
                eta=now + timedelta(seconds=delay_seconds - 5),
                expires=now + timedelta(seconds=delay_seconds + 10)
            )
        else:
            voucher = cls._get_voucher(session, partic.offer, partic.supplier_id)

            if partic.demand_status != DemandStatus.FREE_ONGOING.value and voucher:
                cls._use_voucher(
                    session, partic, pack, voucher, partic.offer, 18
                )
                partic.voucher_id = voucher.id
                # 成功
                status = ParticStatus.NOT_CONTACT.value
                # 队列状态为待呼叫
                queue_status = BidQueueStatus.WAITING
            else:
                if pack.bid_point >= partic.offer:
                    # 成功
                    status = ParticStatus.NOT_CONTACT.value
                    # 队列状态为待呼叫
                    queue_status = BidQueueStatus.WAITING
                    cls.use_bid_point(session, pack, partic, user)
                else:
                    # 余额不足
                    status = ParticStatus.BALANCE_NOT_ENOUGH.value
                    # 队列状态为待充值
                    queue_status = BidQueueStatus.RECHARGE

            update_status()
            tel = cls.bid_virtual_number(session, demand, user)
            cls.msg_notify(session, demand, user, partic, tel, pack)
        # 如果是竞价模式，还需判断是否进入二发（人数不足）
        # 因为竞价分配时，状态是分配中，所以需要用这个状态来判断
        Log.info('demand_id:{}  status:{}'.format(demand.id, demand.status))
        if demand.status == DemandStatus.ASSIGN.value:
            demand_complete(demand)
            gen_renewable_demand(session, demand)
        # 中标时判断是否抢单结束
        bid_count = demand_bid_count(demand.id, demand.published_count, session)
        if demand.cate == DemandCate.MAINTAIN.value:
            limit = bid_limit(demand)
        else:
            limit = BID_USER_COUNT

        if bid_count >= limit:
            demand_complete(demand)
        if push_queue:
            # 写入中标队列
            cls.insert_winner_queue(session, user, partic, queue_status)
        return tel

    @classmethod
    def bid_virtual_number(cls, session, demand, user):
        """
        create by ld_wu 2018-03-20
        虚拟号绑定
        :return:
        """
        owner = model_factory.user.get_by_id(session, demand.uid)
        return bind_virtual_number(
            session,
            demand.id,
            demand.area_id,
            owner.mobile,
            user.mobile
        )

    @classmethod
    def winner_handler(cls, partic_id, demand_id, published_count, mobile):
        """
        负责超时未联系及下一批中标人
        :return:
        """
        Log.info('demand:{}, partic_id:{}'.format(demand_id, partic_id))
        now = datetime.utcnow()
        if mobile:
            bid_winner_call_notify.apply_async(
                (partic_id, mobile),
                eta=now + timedelta(seconds=60 * 5),
                expires=now + timedelta(seconds=60 * 6)
            )
        winner_handle_task.apply_async(
            (partic_id, demand_id, published_count),
            eta=now + timedelta(seconds=60 * 10),
            expires=now + timedelta(seconds=60 * 12)
        )

    @classmethod
    def bronze_assign_winner(cls, partic_id, demand_id, published_count):
        """
        create by ld_wu 2018-04-11
        铜牌用户中标流程
        :return:
        """
        with DB() as session:
            Log.info('demand:{} bronze_assign_winner'.format(demand_id))
            Log.info(partic_id)
            # 铜牌一口价是否有人中标
            has_success = session.query(model_factory.partic.id).filter(
                model_factory.partic.demand_id == demand_id,
                model_factory.partic.published_count == published_count,
                model_factory.partic.status.in_(
                    [
                        ParticStatus.SUCCEED.value,
                        ParticStatus.NOT_CONTACT.value,
                        ParticStatus.BALANCE_NOT_ENOUGH.value,
                        ParticStatus.TIMEOUT_NOT_CONTACT.value
                    ]
                ),
                model_factory.partic.demand_status == DemandStatus.BRONZE_MEDAL_DISCOUNT.value
            ).first()
            # 如果已经有人中标了则直接返回
            if has_success:
                return
            demand = model_factory.demand.get_by_id(session, demand_id)
            partic = model_factory.partic.get_by_args(
                session,
                id=partic_id,
                status=ParticStatus.WAITING.value
            )
            if not partic or not demand:
                return
            # 中标用户已达上限则派单抢单失败
            bid_count = demand_bid_count(demand.id, demand.published_count, session)
            if bid_count >= BID_USER_COUNT:
                demand_complete(demand)
                return
            user = model_factory.user.get_by_id(session, partic.supplier_id)
            tel = cls.be_winner(session, demand, user, partic, False)
            now = datetime.utcnow()
            bid_winner_call_notify.apply_async(
                (partic_id, tel),
                eta=now + timedelta(seconds=60 * 5),
                expires=now + timedelta(seconds=60 * 6)
            )
            bronze_winner_handle_task.apply_async(
                (partic_id, demand_id),
                eta=now + timedelta(seconds=60 * 10),
                expires=now + timedelta(seconds=60 * 12)
            )
        # 新项目提醒（竞价）
        demand_new_notify_task.apply_async(
            (demand_id,),
            eta=now + timedelta(seconds=3),
            expires=now + timedelta(seconds=10)
        )
        # 清除缓存
        clear_cache(demand_id)

    @classmethod
    def dispatch_assign_winner(cls, partic_id, demand_id):
        """
        create by ld_wu 2018-04-11
        派单用户中标流程
        :return:
        """
        with DB() as session:
            Log.info('demand:{} dispatch_assign_winner'.format(demand_id))
            Log.info('partic:{}'.format(partic_id))

            partic = model_factory.partic.get_by_args(
                session,
                id=partic_id,
                status=ParticStatus.WAITING.value
            )
            demand = model_factory.demand.get_by_id(session, demand_id)

            if not partic or not demand:
                return

            if demand.status in (DemandStatus.INVALID.value, DemandStatus.DONE.value):
                return
            # 中标用户已达上限则派单抢单失败
            bid_count = demand_bid_count(demand.id, demand.published_count, session)
            if bid_count >= BID_USER_COUNT:
                demand_complete(demand)
                return

            enterprise = session.query(
                model_factory.enterprise_to_user
            ).filter(
                model_factory.enterprise_to_user.user_id == partic.supplier_id
            ).first()

            # 兼容无企业状况
            if enterprise:
                if partic.enterprise_level != EnterpriseLevel.LEVEL_6.value:
                    # 非地方合作伙伴, 取同企业下中标人
                    enterprise_cond = (model_factory.partic.enterprise_id == enterprise.enterprise_id)
                else:
                    # 地方合作伙伴, 取所有地方合作伙伴企业下中标人
                    enterprise_cond = (model_factory.partic.enterprise_level == EnterpriseLevel.LEVEL_6.value)

                # 同企业下是否已有人抢单成功
                has_success = session.query(model_factory.partic.id).filter(
                    model_factory.partic.demand_id == demand_id,
                    model_factory.partic.status == ParticStatus.SUCCEED.value,
                    model_factory.partic.demand_status.in_([
                        DemandStatus.DISPATCHING.value,
                        DemandStatus.MASTER_DISPATCHING.value
                    ]),
                    enterprise_cond
                ).first()
                # 如果已经有人中标了则直接返回
                if has_success:
                    return

            user = model_factory.user.get_by_id(session, partic.supplier_id)

            # 直接中标, 跳过待联系阶段
            partic.status = ParticStatus.SUCCEED.value
            partic.bid_win_at = int(time.time())
            partic.follow_type = FollowType.KEEP.value
            cls.bid_virtual_number(session, demand, user)
            # 派单抢单以后重新判断人数是否需要进入抢单结束
            demand_to_complete(session, demand)
        # 清除缓存
        clear_cache(demand_id)

    @classmethod
    def _get_voucher(cls, session, bid_point, user_id):
        today = date.today()
        voucher = session.query(
            model_factory.vouchers
        ).filter(
            model_factory.vouchers.status == VoucherStatus.AVAILABLE.value,
            model_factory.vouchers.expired_date >= today,
            model_factory.vouchers.bid_point >= bid_point,
            model_factory.vouchers.user_id == user_id
        ).order_by(
            model_factory.vouchers.expired_date,
            model_factory.vouchers.bid_point
        ).first()
        return voucher

    @classmethod
    def _use_voucher(cls, session, partic, pack, voucher, bid_point, cost_type):
        bid_point = int(bid_point)
        today = date.today()
        voucher.status = VoucherStatus.USED.value
        voucher.used_date = today
        voucher.used_bid_point = bid_point
        model_factory.bid_cost_record.create(
            session,
            user_id=partic.supplier_id,
            cost_type=cost_type,
            bid_point=bid_point,
            left_bid_point=pack.bid_point,
            demand_id=partic.demand_id
        )
        # 消耗券时写入代金券日统计
        common_producer = Producer(COMMON_API_QUEUE)
        common_producer.send(
            json.dumps(
                dict(
                    backend='voucher_stat',
                    func='day_stat',
                    kwargs=dict(
                        user_id=partic.supplier_id,
                        reason=voucher.reason,
                        voucher_stat_type=VoucherStatType.USED.value
                    )
                )
            )
        )

    @classmethod
    def assign_red_packet(cls, red_packet_id, user_id):
        """
        create by ld_wu 2018-07-27
        抢红包后红包分配消费者
        :return:
        """

        def get_history(record, current_list):
            receiver = model_factory.user.get_by_id(session, record.receiver_id)
            if receiver:
                host = os.environ.get('img_host')
                if receiver.avatar:
                    avatar = urllib.parse.urljoin(host, receiver.avatar)
                else:
                    avatar = receiver.avatar
                nickname = receiver.nickname
            else:
                nickname = ''
                avatar = ''
            current_list.append(dict(
                money=record.money,
                received_at=record.received_at,
                best_receiver=bool(record.best_receiver),
                receiver_id=record.receiver_id,
                receiver_nickname=nickname,
                receiver_avatar=avatar
            ))
            return current_list

        with DB() as session:
            red_packet = model_factory.bid_point_red_packet.get_by_id(
                session,
                red_packet_id
            )
            if red_packet and red_packet.status == RedPacketStatus.NORMAL.value:
                # 群红包逻辑
                if red_packet.receiver_id == 0:
                    key = RECEIVE_RED_PACKET_HISTORY.format(
                        red_packet_id=red_packet_id
                    )
                    data = redis_client.get(key)
                    if data is not None:
                        data = json.loads(data)
                    else:
                        data = dict()
                    record_model = model_factory.get_bid_point_red_packet
                    records = session.query(
                        record_model
                    ).filter(
                        record_model.red_packet_id == red_packet_id
                    ).order_by(
                        record_model.id
                    ).all()
                    receiver_ids = [
                        each.receiver_id for each in records
                    ]

                    if user_id in receiver_ids:
                        # 该用户是已经抢过红包了
                        return

                    unreceived_records = [
                        each for each in records if each.receiver_id == 0
                    ]
                    if unreceived_records:
                        # 弹出第一条记录作为领取记录
                        allot_record = unreceived_records.pop(0)
                        # 领取红包记录领取人及领取时间
                        allot_record.receiver_id = user_id
                        allot_record.received_at = int(time.time())
                        pack = model_factory.pack.get_by_args(
                            session,
                            uid=user_id
                        )
                        if not data:
                            red_packet_info = dict(
                                overed_at=red_packet.overed_at,
                                total_money=red_packet.total_money,
                                remark=red_packet.remark,
                                packet_type=red_packet.packet_type,
                                packet_count=red_packet.packet_count,
                                status=red_packet.status,
                                sender_id=red_packet.sender_id,
                                created_at=red_packet.created_at
                            )
                            sender = model_factory.user.get_by_id(session, red_packet.sender_id)
                            if sender:
                                red_packet_info['sender_nickname'] = sender.nickname
                                host = os.environ.get('img_host')
                                if sender.avatar:
                                    avatar = urllib.parse.urljoin(host, sender.avatar)
                                else:
                                    avatar = sender.avatar
                                red_packet_info['sender_avatar'] = avatar
                            else:
                                red_packet_info['sender_nickname'] = ''
                                red_packet_info['sender_avatar'] = ''

                            result = get_history(
                                allot_record, data.get('result', list())
                            )
                            data = dict(
                                polling=True,
                                result=result,
                                red_packet_info=red_packet_info
                            )

                        else:
                            data['result'] = get_history(
                                allot_record, data.get('result', list)
                            )
                        if pack:
                            # 增加用户汇币
                            pack.bid_point += allot_record.money
                            # 写入消费记录
                            model_factory.bid_cost_record.create(
                                session,
                                user_id=user_id,
                                cost_type=21,     # 领取红包
                                bid_point=allot_record.money,
                                left_bid_point=pack.bid_point
                            )

                    # 红包已抢完则需要修改红包状态
                    if not unreceived_records:
                        red_packet.status = RedPacketStatus.OVER.value
                        red_packet.overed_at = (
                            red_packet.overed_at or int(time.time())
                        )
                        red_packet_info = data.get('red_packet_info')
                        if red_packet_info:
                            red_packet_info['status'] = RedPacketStatus.OVER.value
                            red_packet_info['overed_at'] = red_packet.overed_at

                        max_money = max([each.money for each in records])
                        for each in records:
                            if max_money == each.money:
                                each.best_receiver = True
                                break
                        for each in data.get('result', list()):
                            if max_money == each.get('money'):
                                each['best_receiver'] = True
                                break

                    Log.info(data)
                    redis_client.set(
                        key,
                        json.dumps(data)
                    )

    @classmethod
    def expired_red_packet(cls, red_packet_id):
        """
        create by ld_wu 2018-07-25
        红包过期任务（唤醒消费者）
        1、红包状态如果还未领完则把红包状态修改为过期
        2、增加发红包人的汇币数量
        3、写入BidCostRecord
        """
        with DB() as session:
            get_bid_point_red_packet = model_factory.get_bid_point_red_packet
            red_packet = model_factory.bid_point_red_packet.get_by_id(
                session,
                red_packet_id
            )
            if red_packet:
                if red_packet.status == RedPacketStatus.NORMAL.value:
                    if red_packet.receiver_id == 0:
                        # 群红包逻辑
                        expired_money = session.query(
                            func.coalesce(
                                func.sum(get_bid_point_red_packet.money),
                                0).label('total_money')
                        ).filter(
                            get_bid_point_red_packet.red_packet_id == red_packet_id,
                            get_bid_point_red_packet.receiver_id == 0
                        ).first()
                        bid_point = expired_money.total_money
                    else:
                        # 个人红包逻辑
                        bid_point = red_packet.total_money

                    if bid_point:
                        red_packet.overed_at = int(time.time())
                        red_packet.status = RedPacketStatus.EXPIRED.value
                        key = RECEIVE_RED_PACKET_HISTORY.format(
                            red_packet_id=red_packet.id)
                        cache_data = redis_client.get(key)
                        if cache_data:
                            cache_data = json.loads(cache_data)
                            red_packet_info = cache_data.get('red_packet_info')
                            if red_packet_info:
                                red_packet_info[
                                    'status'] = RedPacketStatus.EXPIRED.value
                                red_packet_info[
                                    'overed_at'] = red_packet.overed_at
                                redis_client.set(key, json.dumps(cache_data))
                        pack = model_factory.pack.get_by_args(
                            session,
                            uid=red_packet.sender_id
                        )
                        if pack:
                            pack.bid_point += bid_point
                            # 写入红包返还记录历史
                            model_factory.bid_cost_record.create(
                                session,
                                user_id=red_packet.sender_id,
                                cost_type=22,  # 红包过期退还
                                bid_point=bid_point,
                                left_bid_point=pack.bid_point
                            )
                            im_msg_producer = Producer(IM_MESSAGE)
                            im_msg_producer.send(json.dumps({
                                'func': 'expired_red_packet_notify',
                                'args': [],
                                'kwargs': {
                                    'red_packet_id': red_packet_id,
                                    'bid_point': int(bid_point)
                                }
                            }))
